package com.kitten.user.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.google.common.base.Preconditions;
import com.kitten.count.dto.FindUserCountsByIdRspDTO;
import com.kitten.framework.common.enums.DeletedEnum;
import com.kitten.framework.common.enums.StatusEnum;
import com.kitten.framework.common.exception.BizException;
import com.kitten.framework.common.response.Response;
import com.kitten.framework.common.utils.DateUtils;
import com.kitten.framework.common.utils.JsonUtils;
import com.kitten.framework.common.utils.NumberUtils;
import com.kitten.framework.common.utils.ParamUtils;
import com.kitten.framework.context.holder.LoginUserContextHolder;
import com.kitten.user.constants.RedisKeyConstants;
import com.kitten.user.constants.RoleConstants;
import com.kitten.user.domain.dataobject.RoleDO;
import com.kitten.user.domain.dataobject.UserDO;
import com.kitten.user.domain.dataobject.UserRoleDO;
import com.kitten.user.domain.mapper.RoleDOMapper;
import com.kitten.user.domain.mapper.UserDOMapper;
import com.kitten.user.domain.mapper.UserRoleDOMapper;
import com.kitten.user.dto.req.*;
import com.kitten.user.dto.resp.FindUserByIdRspDTO;
import com.kitten.user.dto.resp.FindUserByPhoneRspDTO;
import com.kitten.user.enums.ResponseCodeEnum;
import com.kitten.user.enums.SexEnum;
import com.kitten.user.model.vo.FindUserProfileReqVO;
import com.kitten.user.model.vo.FindUserProfileRspVO;
import com.kitten.user.model.vo.UpdateUserInfoReqVO;
import com.kitten.user.rpc.CountRpcService;
import com.kitten.user.rpc.DistributedIdGeneratorRpcService;
import com.kitten.user.rpc.OssRpcService;
import com.kitten.user.service.UserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.util.Lists;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Slf4j
@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UserDOMapper userDOMapper;
    @Resource
    private UserRoleDOMapper userRoleDOMapper;
    @Resource
    private RoleDOMapper roleDOMapper;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource(name = "taskExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Resource   // 注入分布式 ID 生成器 RPC 服务
    private DistributedIdGeneratorRpcService distributedIdGeneratorRpcService;
    @Resource
    private OssRpcService ossRpcService;
    @Resource
    private CountRpcService countRpcService;

    // 初始化本地缓存二级缓存
    private static final Cache<Long, FindUserByIdRspDTO> LOCAL_CACHE = Caffeine.newBuilder()
            .initialCapacity(10000) // 设置初始容量为 10000 个条目
            .maximumSize(10000) // 设置缓存的最大容量为 10000 个条目
            .expireAfterWrite(1, TimeUnit.HOURS) // 设置缓存条目在写入后 1 小时过期
            .build();


    /**
     * 实现用户注册接口
     * @param reqDTO
     * @return
     */
    @Override
    @Transactional
    public Response<Long> register(RegisterUserReqDTO reqDTO) {
        String phone = reqDTO.getPhone();

        // 先判断该手机号是否已被注册
        UserDO userDO1 = userDOMapper.selectByPhone(phone);

        log.info("==> 用户是否注册, phone: {}, userDO: {}", phone, JsonUtils.toJsonString(userDO1));

        // 若已注册，则直接返回用户 ID
        if (Objects.nonNull(userDO1)) {
            return Response.success(userDO1.getId());
        }

        // 否则注册新用户
        // 获取全局自增的kittenBookID      ====>       使用分布式ID生成 kittenBookId
//        Long kittenbookId = redisTemplate.opsForValue().increment(RedisKeyConstants.KITTENBOOK_ID_GENERATOR_KEY);
        String kittenbookId = distributedIdGeneratorRpcService.getKittenbookId();
        // 生成用户ID
        String userIdStr = distributedIdGeneratorRpcService.getUserId();
        Long userId = Long.valueOf(userIdStr);

        UserDO userDO = UserDO.builder()
                .id(userId)
                .phone(phone)
                .kittenbookId(String.valueOf(kittenbookId)) // 自动生成KittenBook号 ID
                .nickname("kitten" + kittenbookId) // 自动生成昵称, 如：kitten10000
                .status(StatusEnum.ENABLE.getValue()) // 状态为启用
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .isDeleted(DeletedEnum.NO.getValue()) // 逻辑删除
                .build();

        // 添加入库
        userDOMapper.insert(userDO);

        // 获取刚刚添加入库的用户 ID
//        userId = userDO.getId();

        // 给该用户分配一个默认角色
        UserRoleDO userRoleDO = UserRoleDO.builder()
                .userId(userId)
                .roleId(RoleConstants.COMMON_USER_ROLE_ID)
                .createTime(LocalDateTime.now())
                .updateTime(LocalDateTime.now())
                .isDeleted(DeletedEnum.NO.getValue())
                .build();
        userRoleDOMapper.insert(userRoleDO);

        RoleDO roleDO = roleDOMapper.selectByPrimaryKey(RoleConstants.COMMON_USER_ROLE_ID);

        // 将该用户的角色 ID 存入 Redis 中
        List<String> roles = new ArrayList<>(1);
        roles.add(roleDO.getRoleKey());

        String userRolesKey = RedisKeyConstants.buildUserRoleKey(userId);
        redisTemplate.opsForValue().set(userRolesKey, JsonUtils.toJsonString(roles));

        return Response.success(userId);
    }

    /**
     * 实现用户信息更新接口
     * @param reqVO       (kittenbookId, avatarImg....)
     * @return
     */
    @Override
    public Response<?> updateUserInfo(UpdateUserInfoReqVO reqVO) {
        log.info("====> 用户信息更新 {}", reqVO);
        UserDO userDO = new UserDO();
        // 设置需要修改的user的 id
        userDO.setId(LoginUserContextHolder.getUserId());
        // 定义是否修改flag
        boolean needUpdate = false;


        // 头像
        MultipartFile avatarFile = reqVO.getAvatar();
        if (Objects.nonNull(avatarFile)) {
            // todo : 调用文件上传服务
            String avatar = ossRpcService.uploadFile(avatarFile);
            if (StringUtils.isBlank(avatar)) {
                throw new BizException(ResponseCodeEnum.UPLOAD_AVATAR_FAIL);
            }
            userDO.setAvatar(avatar);
            needUpdate = true;
        }

        // 昵称
        String nickname = reqVO.getNickname();
        if (StringUtils.isNotBlank(nickname)) {
            Preconditions.checkArgument(ParamUtils.checkNickname(nickname), ResponseCodeEnum.NICK_NAME_VALID_FAIL.getErrorMessage());
            userDO.setNickname(nickname);
            needUpdate = true;
        }
        // KittenBook号
        String kittenbookId = reqVO.getKittenbookId();
        if (StringUtils.isNotBlank(kittenbookId)) {
            Preconditions.checkArgument(ParamUtils.checkKittenbookId(kittenbookId), ResponseCodeEnum.XIAOHASHU_ID_VALID_FAIL.getErrorMessage());
            userDO.setKittenbookId(kittenbookId);
            needUpdate = true;
        }
        // 性别
        Integer sex = reqVO.getSex();
        if (Objects.nonNull(sex)) {
            Preconditions.checkArgument(SexEnum.isValid(sex), ResponseCodeEnum.SEX_VALID_FAIL.getErrorMessage());
            userDO.setSex(sex);
            needUpdate = true;
        }

        // 生日
        LocalDate birthday = reqVO.getBirthday();
        if (Objects.nonNull(birthday)) {
            userDO.setBirthday(birthday);
            needUpdate = true;
        }

        // 个人简介
        String introduction = reqVO.getIntroduction();
        if (StringUtils.isNotBlank(introduction)) {
            Preconditions.checkArgument(ParamUtils.checkLength(introduction, 100), ResponseCodeEnum.INTRODUCTION_VALID_FAIL.getErrorMessage());
            userDO.setIntroduction(introduction);
            needUpdate = true;
        }

        // 背景图
        MultipartFile backgroundImgFile = reqVO.getBackgroundImg();
        if (Objects.nonNull(backgroundImgFile)) {
            // todo: 调用对象存储服务上传文件
            String backgroundImg = ossRpcService.uploadFile(avatarFile);
            if (StringUtils.isBlank(backgroundImg)) {
                throw new BizException(ResponseCodeEnum.UPLOAD_BACKGROUND_IMG_FAIL);
            }
            userDO.setBackgroundImg(backgroundImg);
            needUpdate = true;
        }

        if (needUpdate) {
            // 更新用户信息
            userDO.setUpdateTime(LocalDateTime.now());
            userDOMapper.updateByPrimaryKeySelective(userDO);
            log.info("更新完成");
        }


        return Response.success();
    }

    /**
     * 实现根据手机号查询用户接口
     * @param reqDTO
     * @return
     */
    @Override
    public Response<FindUserByPhoneRspDTO> findByPhone(FindUserByPhoneReqDTO reqDTO) {
        String phone = reqDTO.getPhone();
        // 查询用户信息
        UserDO userDO = userDOMapper.selectByPhone(phone);
        if (Objects.isNull(userDO)) {
            throw new BizException(ResponseCodeEnum.USER_NOT_EXIST);
        }
        // 构建返参
        FindUserByPhoneRspDTO rspDTO = FindUserByPhoneRspDTO.builder()
                .id(userDO.getId())
                .password(userDO.getPassword())
                .build();


        return Response.success(rspDTO);
    }

    /**
     * 实现更新用户密码接口
     * @param updateUserPasswordReqDTO
     * @return
     */
    @Override
    public Response<?> updateUserPassword(UpdateUserPasswordReqDTO updateUserPasswordReqDTO) {
        Long userId = LoginUserContextHolder.getUserId();
        log.info("==> 更新用户密码, userId: {}", userId);
        UserDO userDO = UserDO.builder()
                .id(userId)
                .password(updateUserPasswordReqDTO.getEncodePassword()) // 加密后的密码
                .updateTime(LocalDateTime.now())
                .build();
        userDOMapper.updateByPrimaryKeySelective(userDO);
        return Response.success();
    }

    /**
     * 实现根据用户 ID 查询用户接口
     * @param findUserByIdReqDTO
     * @return
     */
    @Override
    public Response<FindUserByIdRspDTO> findById(FindUserByIdReqDTO findUserByIdReqDTO) {
        Long userId = findUserByIdReqDTO.getId();

        // 1. 本地缓存查询
        FindUserByIdRspDTO findUserByIdRspDTOLocalCache = LOCAL_CACHE.getIfPresent(userId);
        if (Objects.nonNull(findUserByIdRspDTOLocalCache)) {
            log.info("====> 命中本地缓存, {}", findUserByIdRspDTOLocalCache);
            return Response.success(findUserByIdRspDTOLocalCache);
        }

        // 用户缓存 Redis key
        String userInfoRedisKey = RedisKeyConstants.buildUserInfoKey(userId);
        // 2. 查询Redis缓存
        String userInfoRedisValue = (String) redisTemplate.opsForValue().get(userInfoRedisKey);
        if (StringUtils.isNotBlank(userInfoRedisValue)) {
            log.info("==> 从 Redis 中获取用户信息, userInfoRedisKey: {}, userInfoObj: {}", userInfoRedisKey, userInfoRedisValue);
            FindUserByIdRspDTO rspDTO = JsonUtils.parseObject(userInfoRedisValue, FindUserByIdRspDTO.class);
            threadPoolTaskExecutor.execute(() -> {  // 异步更新本地缓存
                LOCAL_CACHE.put(userId, rspDTO);
            });
            return Response.success(rspDTO);
        }

        // 根据用户 ID 查询用户信息
        UserDO userDO = userDOMapper.selectByPrimaryKey(userId);
        // 判空
        if (Objects.isNull(userDO)) {
//            throw new BizException(ResponseCodeEnum.USER_NOT_EXIST);
            threadPoolTaskExecutor.execute(() -> {
                // 防止缓存穿透, 空数据存入Redis
                int expireSeconds = 60 + RandomUtil.randomInt(60);
                redisTemplate.opsForValue().set(userInfoRedisKey, "null", expireSeconds, TimeUnit.SECONDS);
            });
            throw new BizException(ResponseCodeEnum.USER_NOT_EXIST);
        }
        // 构建返参
        FindUserByIdRspDTO findUserByIdRspDTO = FindUserByIdRspDTO.builder()
                .id(userDO.getId())
                .nickName(userDO.getNickname())
                .avatar(userDO.getAvatar())
                .introduction(userDO.getIntroduction())
                .build();

        // 异步将用户信息存入 Redis 缓存，提升响应速度
        threadPoolTaskExecutor.submit(() -> {
            // 过期时间（保底1天 + 随机秒数，将缓存过期时间打散，防止同一时间大量缓存失效，导致数据库压力太大）
            long expireSeconds = 60*60*24 + RandomUtil.randomInt(60*60*24);
            redisTemplate.opsForValue()
                    .set(userInfoRedisKey, JsonUtils.toJsonString(findUserByIdRspDTO), expireSeconds, TimeUnit.SECONDS);
        });

        return Response.success(findUserByIdRspDTO);
    }

    /**
     * 根据ids批量查询用户信息
     * @param findUsersByIdsReqDTO
     * @return
     */
    @Override
    public Response<List<FindUserByIdRspDTO>> findByIds(FindUsersByIdsReqDTO findUsersByIdsReqDTO) {
        List<Long> ids = findUsersByIdsReqDTO.getIds();     // 用户id集合

        // Redis中查询用户详情 构建RedisKEY
        List<String> redisKeys = ids.stream().map(RedisKeyConstants::buildUserInfoKey).toList();
        List<Object> userInfoList = redisTemplate.opsForValue().multiGet(redisKeys);
        if (CollUtil.isNotEmpty(userInfoList)) {    // 不为空时过滤空数据
            userInfoList = userInfoList.stream().filter(Objects::nonNull).toList();
        }
        // 返参
        List<FindUserByIdRspDTO> findUserByIdRspDTOS = Lists.newArrayList();
        // 缓存集合转DTO集合
        if(CollUtil.isNotEmpty(userInfoList)) {
            findUserByIdRspDTOS = userInfoList.stream()
                    .map(value -> JsonUtils.parseObject(String.valueOf(value), FindUserByIdRspDTO.class))
                    .collect(Collectors.toList());
        }
        // 如果需要查询的用户信息都在Redis中，则直接返回
        if (CollUtil.size(ids) == CollUtil.size(findUserByIdRspDTOS)) {
            return Response.success(findUserByIdRspDTOS);
        }

        // 还有另外两种情况：一种是缓存里没有用户信息数据，还有一种是缓存里数据不全，需要从数据库中补充
        // 筛选出缓存里没有的用户数据，去查数据库
        List<Long> needQueryIds = null;
        if (CollUtil.isNotEmpty(findUserByIdRspDTOS)) {
            Map<Long, FindUserByIdRspDTO> map = findUserByIdRspDTOS.stream().collect(Collectors.toMap(FindUserByIdRspDTO::getId, p -> p));
            // 筛选出需要查询的用户id
            needQueryIds = ids.stream().filter(id -> Objects.isNull(map.get(id))).toList();
        } else {    // 缓存中没有用户数据
            needQueryIds = ids;
        }
        // 数据库批量查询
        List<UserDO> userDOS = userDOMapper.selectByIds(needQueryIds);

        // 构建返回数据
        List<FindUserByIdRspDTO> findUserByIdRspDTOS2 = null;
        if (CollUtil.isNotEmpty(userDOS)) { // 数据库有数据
            findUserByIdRspDTOS2 = userDOS.stream().map(userDO -> {    // DO转DTO
                return FindUserByIdRspDTO.builder()
                        .id(userDO.getId())
                        .nickName(userDO.getNickname())
                        .avatar(userDO.getAvatar())
                        .introduction(userDO.getIntroduction())
                        .build();
            }).collect(Collectors.toList());
            // TODO: 异步线程将用户信息存入Redis缓存
            List<FindUserByIdRspDTO> finalFindUserByIdRspDTOS = findUserByIdRspDTOS2;
            threadPoolTaskExecutor.execute(() -> {
                // DTO转Map
                Map<Long, FindUserByIdRspDTO> map = finalFindUserByIdRspDTOS.stream().collect(Collectors.toMap(FindUserByIdRspDTO::getId, p -> p));
                // 执行pipeLine操作，批量存入Redis
                redisTemplate.executePipelined(new SessionCallback<>() {
                    @Override
                    public Object execute(RedisOperations operations) throws DataAccessException {
                        for (UserDO userDO : userDOS) {
                            Long userId = userDO.getId();
                            String userInfoRedisKey = RedisKeyConstants.buildUserInfoKey(userId);
                            FindUserByIdRspDTO findUserInfoByIdRspDTO = map.get(userId);
                            String value = JsonUtils.toJsonString(findUserInfoByIdRspDTO);
                            // 过期时间
                            long expireSeconds = 60*60*6 + RandomUtil.randomInt(60*60*6);
                            operations.opsForValue().set(userInfoRedisKey, value, expireSeconds, TimeUnit.SECONDS);
                        }
                        return null;
                    }
                });
            });
        }
        // 合并数据
        if (CollUtil.isNotEmpty(findUserByIdRspDTOS2)) {
            findUserByIdRspDTOS.addAll(findUserByIdRspDTOS2);
        }

        return Response.success(findUserByIdRspDTOS);
    }

    // 本地缓存-用户信息
    private static final Cache<Long, FindUserProfileRspVO> PROFILE_LOCAL_CACHE = Caffeine.newBuilder()
            .initialCapacity(1000) // 设置初始容量为 10000 个条目
            .maximumSize(1000) // 设置缓存的最大容量为 10000 个条目
            .expireAfterWrite(5, TimeUnit.MINUTES) // 设置缓存条目在写入后 5 分钟过期
            .build();
    /**
     * 查询个人主页信息
     * @param findUserProfileReqVO
     * @return
     */
    @Override
    public Response<FindUserProfileRspVO> findUserProfile(FindUserProfileReqVO findUserProfileReqVO) {
        // 要查询的用户 ID
        Long userId = findUserProfileReqVO.getUserId();

        // 若入参中用户 ID 为空，则查询当前登录用户
        if (Objects.isNull(userId)) {
            userId = LoginUserContextHolder.getUserId();
        }

        // 1. 优先查本地缓存
        if (!Objects.equals(userId, LoginUserContextHolder.getUserId())) { // 如果是用户本人查看自己的主页，则不走本地缓存（对本人保证实时性）
            FindUserProfileRspVO userProfileLocalCache = PROFILE_LOCAL_CACHE.getIfPresent(userId);
            if (Objects.nonNull(userProfileLocalCache)) {
                log.info("## 用户主页信息命中本地缓存: {}", JsonUtils.toJsonString(userProfileLocalCache));
                return Response.success(userProfileLocalCache);
            }
        }

        // 2. 再查询 Redis 缓存
        String userProfileRedisKey = RedisKeyConstants.buildUserProfileKey(userId);

        String userProfileJson = (String) redisTemplate.opsForValue().get(userProfileRedisKey);

        if (StringUtils.isNotBlank(userProfileJson)) {
            FindUserProfileRspVO findUserProfileRspVO = JsonUtils.parseObject(userProfileJson, FindUserProfileRspVO.class);
            // 异步同步到本地缓存
            syncUserProfile2LocalCache(userId, findUserProfileRspVO);
            // 如果是作者本人查看，保证计数的实时性
            authorGetActualCountData(userId, findUserProfileRspVO);

            return Response.success(findUserProfileRspVO);
        }

        // 3. 若 Redis 中无缓存，再查询数据库
        UserDO userDO = userDOMapper.selectByPrimaryKey(userId);

        if (Objects.isNull(userDO)) {
            throw new BizException(ResponseCodeEnum.USER_NOT_FOUND);
        }

        // 构建返参 VO
        FindUserProfileRspVO findUserProfileRspVO = FindUserProfileRspVO.builder()
                .userId(userDO.getId())
                .avatar(userDO.getAvatar())
                .nickname(userDO.getNickname())
                .kittenbookId(userDO.getKittenbookId())
                .sex(userDO.getSex())
                .introduction(userDO.getIntroduction())
                .build();

        // 计算年龄
        LocalDate birthday = userDO.getBirthday();
        findUserProfileRspVO.setAge(Objects.isNull(birthday) ? 0 : DateUtils.calculateAge(birthday));

        // RPC: Feign 调用计数服务
        // 关注数、粉丝数、收藏与点赞总数；获得的点赞数、收藏数
        rpcCountServiceAndSetData(userId, findUserProfileRspVO);

        // 异步同步到 Redis 中
        syncUserProfile2Redis(userProfileRedisKey, findUserProfileRspVO);
        // 异步同步到本地缓存
        syncUserProfile2LocalCache(userId, findUserProfileRspVO);

        return Response.success(findUserProfileRspVO);
    }

    // 异步: 同步用户信息到本地缓存
    private void syncUserProfile2LocalCache(Long userId, FindUserProfileRspVO findUserProfileRspVO) {
        threadPoolTaskExecutor.submit(() -> {
            PROFILE_LOCAL_CACHE.put(userId, findUserProfileRspVO);
        });
    }

    // 作者本人查看，保证计数的实时性
    private void authorGetActualCountData(Long userId, FindUserProfileRspVO findUserProfileRspVO) {
        if (Objects.equals(userId, LoginUserContextHolder.getUserId())) {
            rpcCountServiceAndSetData(userId, findUserProfileRspVO);
        }
    }

    private void rpcCountServiceAndSetData(Long userId, FindUserProfileRspVO findUserProfileRspVO) {
        FindUserCountsByIdRspDTO findUserCountsByIdRspDTO = countRpcService.findUserCountById(userId);

        if (Objects.nonNull(findUserCountsByIdRspDTO)) {
            Long fansTotal = findUserCountsByIdRspDTO.getFansTotal();
            Long followingTotal = findUserCountsByIdRspDTO.getFollowingTotal();
            Long likeTotal = findUserCountsByIdRspDTO.getLikeTotal();
            Long collectTotal = findUserCountsByIdRspDTO.getCollectTotal();
            Long noteTotal = findUserCountsByIdRspDTO.getNoteTotal();

            findUserProfileRspVO.setFansTotal(NumberUtils.formatNumberString(fansTotal));
            findUserProfileRspVO.setFollowingTotal(NumberUtils.formatNumberString(followingTotal));
            findUserProfileRspVO.setLikeAndCollectTotal(NumberUtils.formatNumberString(likeTotal + collectTotal));
            findUserProfileRspVO.setNoteTotal(NumberUtils.formatNumberString(noteTotal));
            findUserProfileRspVO.setLikeTotal(NumberUtils.formatNumberString(likeTotal));
            findUserProfileRspVO.setCollectTotal(NumberUtils.formatNumberString(collectTotal));
        }
    }
    // 异步: 同步用户信息到 Redis
    private void syncUserProfile2Redis(String userProfileRedisKey, FindUserProfileRspVO findUserProfileRspVO) {
        threadPoolTaskExecutor.submit(() -> {
            // 设置随机过期时间 (2小时以内)
            long expireTime = 60*60 + RandomUtil.randomInt(60 * 60);

            // 将 VO 转为 Json 字符串写入到 Redis 中
            redisTemplate.opsForValue().set(userProfileRedisKey, JsonUtils.toJsonString(findUserProfileRspVO), expireTime, TimeUnit.SECONDS);
        });
    }




}
